using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Linq;
using FRW.Common.Configuration;
using FRW.Common.Extensions;
using FRW.Common;

namespace Money.Plugins.Import.RbsImport
{
    // TODO updates to the configuration are not picked up straight away
    // TODO add new matching option that can match items based on description, type and amount (with range option)
    // TODO we need to remove the dependancy of having the application configuration and this should maybe be through the IDataSet interface?
    public partial class RbsImport : ImportTransactions, IPluginImport, IPluginDataAccess
    {
        private const string _versionId = "0.1.0";
        private const string _importHandlerName = "rbs-csv";
        private static Configuration<Configuration> _configuration;
        private List<RbsTransaction> _importTransactions = new List<RbsTransaction>();
        private bool _importCacheValid = false;
        private IPluginProgress _progressInterface;

        public string Name { get { return _importHandlerName; } }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="accountName"></param>
        /// <param name="config"></param>
        public RbsImport()
            : base(_importHandlerName)
        {
            if (_importFiles != null)
            {
                _importFiles.Clear();
            }
            else
            {
                _importFiles = new List<ImportFile>();
            }

            LoadConfiguration();
        }

        /// <summary>
        /// Load the configuration for the plugin from the configuration file. This is a static as we want all instances of the plugin 
        /// to use the same configuration file
        /// </summary>
        private void LoadConfiguration()
        {
            if (_configuration == null)
            {
                _configuration = new Configuration<Configuration>();

                if (!_configuration.Load())
                {
                    _configuration.LoadDefault();
                }

                if (_configuration == null)
                {
                    throw new InvalidOperationException();
                }
            }
        }

        private RbsTransaction ExtractTransaction(Match match, RbsTransaction it, string account)
        {
            Int32 count = match.Groups.Count;

            if (count == _configuration.Item.FieldCount)
            {
                try
                {
                    it = new RbsTransaction();
                    it.RawString = match.ToString();
                    it.Amount = Convert.ToDecimal(match.Groups["amount"].Value);
                    it.Date = DateTime.Parse(match.Groups["date"].Value);
                    it.Description = match.Groups["desc"].Value;
                    it.Type = match.Groups["type"].Value;
                    it.Account = account;
                    it.Id = _configuration.Item.GetNewId();
                }
                catch
                {
                    // if we fail then the transaction is not loaded
                }

            }
            return it;
        }

        private Transaction PopulateTransaction(RbsTransaction it, Transaction trans)
        {
            if (trans == null)
            {
                throw new ArgumentNullException();
            }

            trans.Amount = it.Amount;
            trans.Date = it.Date;
            trans.Account = it.Account;
            trans.Description = NameUpdate(it.Description, _dataAccessInterface.DescriptionMatches);
            trans.Type = DefaultTypeLookup(it.Type);

            // now determine category and possible updated type based on description
            CategoriseTransaction(it, trans);
            trans.ImportId = it.Id;
            trans.ImportHandler = _importHandlerName;

            return trans;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="transactionList"></param>
        private void CategoriseTransaction(RbsTransaction rbsTransaction, Transaction transaction)
        {
            // TODO should we make the assumption that the list is sorted or is it safer to sort by priority
            var descriptionMatches = from matchSet in _configuration.Item.Categories orderby matchSet.Priority  descending select matchSet;

            foreach (TransactionMatch match in descriptionMatches.ToList())
            {
                if (match.IsMatch(rbsTransaction))
                {
                    // TODO maybe this should be done in transaction match class?
                    if (match.CategoryId != Convert.ToInt32(Categories.DefaultCategories.Blank))
                    {
                        if (match.CategoryId != null)
                        {
                            transaction.CategoryId = (int)match.CategoryId;
                        }
                    }

                    if (match.Type != null)
                    {
                        transaction.Type = (Transaction.TransactionType)match.Type;
                    }

                    // if this is marked as terminate then we do not want to apply any further rules matching the description 
                    if (match.Terminate)
                    {
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="transTypeStr"></param>
        /// <returns></returns>
        private Transaction.TransactionType DefaultTypeLookup(string transTypeStr)
        {
            Transaction.TransactionType transType;

            switch (transTypeStr)
            {
                case "BAC":
                    transType = Transaction.TransactionType.Credit;
                    break;

                case "INT":
                    transType = Transaction.TransactionType.Interest;
                    break;

                case "DPC":
                    transType = Transaction.TransactionType.Payment;
                    break;

                case "POS":
                    transType = Transaction.TransactionType.Debit;
                    break;

                case "CHQ":
                    transType = Transaction.TransactionType.Cheque;
                    break;

                case "CHG":
                    transType = Transaction.TransactionType.Charge;
                    break;

                case "D/D":
                    transType = Transaction.TransactionType.DirectDebit;
                    break;

                case "S/O":
                    transType = Transaction.TransactionType.StandingOrder;
                    break;

                case "C/L":
                    transType = Transaction.TransactionType.CashWithdrawal;
                    break;

                default:
                    transType = Transaction.TransactionType.Unknown;
                    break;
            }

            return transType;
        }

        #region IImport
        void IPluginImport.ResetHandler()
        {
            _importCacheValid = false;
            _importTransactions.Clear();

            base.ResetHandler();
        }

        void IPluginImport.SetProgressInterface(IPluginProgress progressInterface)
        {
            _progressInterface = progressInterface;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        bool IPluginImport.ValidateFile(string filename)
        {
            bool result = false;
            int totalLines = 0;
            int validLines = 0;
            Match match;

            using (StreamReader sr = new StreamReader(filename))
            {
                while (!sr.EndOfStream)
                {
                    string line = sr.ReadLine();

                    totalLines++;
                    match = Regex.Match(line, _configuration.Item.MatchString);
                    Int32 count = match.Groups.Count;
                    if (count == _configuration.Item.FieldCount)
                    {
                        validLines++;
                    }
                }

                if ((100 * validLines / totalLines) > 50)
                {
                    result = true;
                }
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        void IPluginImport.DoImport()
        {
            _importCacheValid = false;
            _importTransactions = new List<RbsTransaction>();

            List<RbsTransaction> fileTransactions;

            if (_importFiles.Count != 0)
            {
                int total = _importFiles.Count;
                int count = 0;

                foreach (ImportFile importFile in _importFiles)
                {
                    if (_progressInterface != null)
                    {
                        _progressInterface.ProgressText = "Loading import statement file [" + (count + 1).ToString() + " / " + total + "]";
                    }

                    fileTransactions = new List<RbsTransaction>();
                    string file = importFile.FileName;
                    string account = importFile.Account;

                    if (IsAlreadyImported(file))
                    {
                        importFile.IsSkipped = true;
                        continue;
                    }

                    using (StreamReader sr = new StreamReader(file))
                    {
                        while (!sr.EndOfStream)
                        {
                            string line = sr.ReadLine();

                            Match match = Regex.Match(line, _configuration.Item.MatchString);

                            RbsTransaction it = null;

                            it = ExtractTransaction(match, it, account);

                            if (it != null)
                            {
                                fileTransactions.Add(it);
                            }
                        }
                    }

                    _importTransactions.AddRange(fileTransactions);
                    SaveImportedRecords(fileTransactions, file);

                    if (_progressInterface != null)
                    {
                        _progressInterface.Percentage = 100 * count++ / total;
                    }
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        IEnumerable<Transaction> IPluginImport.ImportedTransactions
        {
            get
            {
                int total = _importTransactions.Count;
                int count = 0;

                if (_progressInterface != null)
                {
                    _progressInterface.ProgressText = "Converting RBS Current Account Transactions";
                }

                foreach (RbsTransaction it in _importTransactions)
                {
                    Transaction trans = new Transaction();

                    PopulateTransaction(it, trans);

                    if (_progressInterface != null)
                    {
                        _progressInterface.Percentage = 100 * count++ / total;
                    }

                    yield return trans;
                }
            }
        }

        string IPluginImport.GetOriginalTransaction(long id)
        {
            RbsTransaction importTransaction = GetImportedTransaction(id);

            string display = "";

            if(importTransaction != null)
            {
                display += "Date: " + importTransaction.Date.ToShortDateString() + Environment.NewLine;
                display += "Amount: " + importTransaction.Amount.ToString() + Environment.NewLine;
                display += "Description: " + importTransaction.Description + Environment.NewLine;
                display += "Type: " + importTransaction.Type.ToString() + Environment.NewLine;
                display += "Raw Text: " + importTransaction.RawString;
            }

            return display;
        }

        void IPluginImport.ResetTransaction(Transaction current)
        {
            RbsTransaction importedTransaction = GetImportedTransaction(current.ImportId);

            PopulateTransaction(importedTransaction, current);
        }
        #endregion

        private RbsTransaction GetImportedTransaction(long id)
        {
            RbsTransaction tt = null;

            LoadImportedRecords();

            var transaction = from trans in _importTransactions where trans.Id.Equals(id) select trans;
            if (transaction.Count() > 0)
            {
                tt = transaction.First();
            }

            return tt;
        }

        public void LoadImportedRecords()
        {
            if (_importCacheValid == false)
            {
                List<RbsTransaction> tempImported;
                _importTransactions = new List<RbsTransaction>();

                foreach (string file in GetImportedFileList())
                {
                    tempImported = (List<RbsTransaction>)Serialize.FromXMLFile(typeof(List<RbsTransaction>), file);

                    if (tempImported != null)
                    {
                        _importTransactions.AddRange(tempImported);
                    }
                }

                // HACK check whether we have any import ids higher than the current value in configuration
                foreach (RbsTransaction transaction in _importTransactions)
                {
                    if (transaction.Id > _configuration.Item.Id)
                    {
                        // if we find an Id higher than our current setting then we need to correct
                        _configuration.Item.Id = transaction.Id;
                    }
                }

                _importCacheValid = true;
            }
        }
    }
}
